#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <pthread.h>
#include <malloc.h>


struct enlace
{
  	char ip[11];
  	int port;
  	int dest[3];
  	int mtu[3];
};

struct enlace enlaces[6];


/*
 * Funcao que le o arquivo de Configuracao Inicial, armazenando os valores lidos na memoria
 */
void loadCI (char file[])
{
  	int index = 0, no_dest = 0, i = 0, j = 0, k = 0;
  	char *aux = (char*)malloc(sizeof(char) * 255);
  	char *info = NULL;
  	char *separadores = " ,:;>"; /* Separadores dos tokens */
  	FILE *pFile;

  	free(info);

  	/* Inicializacao do vetor de estrutura */
  	for (i = 0; i < 6; i++)
  	{
      		for(k = 0; k < 11; k++)
      		{
        		enlaces[i].ip[k] = 0;
		}
		for(j = 0; j < 3; j++)
  		{
        		enlaces[i].dest = 0;
        		enlaces[i].mtu = 0;
		}
		enlaces[i].port = 0;
  	}

  	/* Abertura do arquivo de configuraçao inicial para leitura */
  	pFile = fopen("topologia.cnf", "r");
  	if(!pFile)
  	{
    		printf("Arquivo nao encontrado!!!\n");
  	}
  	else
  	{
      		fgets(aux, 255, pFile);
      		aux[strlen(aux)-1] = '\0';

      		/* Rotina de transferencia dos dados da topologia da rede para a memoria (vetor de estrutura) */
      		if(strcmp(aux,"Nos") == 0)
      		{
          		fgets(aux, 255, pFile);

          		info = strtok(aux, separadores);

          		while(strcmp(info,"Enlaces") != 0)
          		{
		      		index = atoi(info);
		      		index -= 1;
		      		info = strtok(NULL, separadores);
		      		strcpy(enlaces[index].ip, info);

		      		info = strtok(NULL, separadores);
		      		enlaces[index].port = atoi(info);

		      		fgets(aux, 255, pFile);
		      		aux[strlen(aux)-1] = '\0';

		      		info = strtok(aux, separadores);
          		}
      		}
      		else printf("Formatacao da CI esta errada!!!\n");
  	}

  	fgets(aux, 255, pFile);
  	info = strtok(aux, separadores);

  	while(strcmp(info,"Fim") != 0)
  	{
	      	index = atoi(info);
	      	index -= 1;
	      	info = strtok(NULL, separadores);

	      	no_dest = atoi(info);
	      	no_dest -= 1;

	      	enlaces[index].dest[no_dest] = 1;
	      	enlaces[no_dest].dest[index] = 1;

	      	info = strtok(NULL, separadores);
	      	enlaces[index].mtu[no_dest] = atoi(info);
	      	enlaces[no_dest].mtu[index] = atoi(info);

		/* Leitura sempre no final do bloco, pra nao tentar processar dados depois de "Fim" */
	      	fgets(aux, 255, pFile);
	      	info = strtok(aux, separadores);
  	}

  	/* Verificaçao feita sobre o vetor de estrutura para saber se foi corretamente populado */
  	for (i = 0; i < 6; i++)
  	{
      		printf("NO: %d - IP: %s - PORTA: %d\n", i+1, enlaces[i].ip, enlaces[i].port);
		for(j = 0; j < 3; j++)
		{
			if (enlaces[i].dest[j] != 0)
				printf("  NO VIZINHO %d: %d - MTU DO ENLACE: %d\n", j, enlaces[i].dest[j] enlaces[i].mtu[j]);
		}
  	}
  	fclose(pFile);
	return;
}


/*
 * Thread que Envia recebe o datagrama da Camada de Rede, o processa e depois o envia para a Camada Física
 */
void *send(void *node)
{

	struct frame
	{
		void *redeDatagram;
		int checksum;
		int type;
	}FrameEnlace;

	int packetSize;
	int dst_node;
	char ip[11];
	int port;
	int i;
	bool vizinho = false;

	while(1)
	{
		/* Recebe da Camada de Rede o datagrama a ser enviado e o nº do nó destino
		 * (leitura do buffer compartilhado) */
		FrameEnlace.redeDatagram = &bufS_RE.data;

		packetSize = BufferSend_Rede_Enlace.packetSize;
		dst_node = BufferSend_Rede_Enlace.dst_node;

		/* Traduz o nº do nó destino para o endereço IP e porta (de acordo com as informações
		 * obtidas no arquivo C.I.) */
		strcpy (ip, enlaces[dst_node-1].ip);
		port = enlaces[dst_node-1].port;


		/* Verifica se o nó é vizinho (acessível) */
		for(i = 0; i < 3; i++)
		{
			if (enlaces[node-1].dest[i] == dst_node)
			{
				vizinho = true;
				break;
			}
		}

		if(vizinho)
		{
			/* Verifica se o tamanho do datagrama recebido da Camada de Rede é compatível com o MTU
			 * suportado pelo enlace */
			if(packetSize <= enlaces[node-1].mtu[i])
			{

				/* Cria um socket UDP (dgram) */
			    	if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
			    	{
					perror("socket()");
					exit(1);
			    	}

			    	/* Define o endereço IP e a porta do no destino */
			   	dst.sin_family      = AF_INET;        /* Tipo do endereço */
			    	dst.sin_port        = port;           /* Porta do nó destino */
			    	dst.sin_addr.s_addr = inet_addr(ip);  /* Endereço IP do nó destino */

				/* Calcula o Checksum e o adiciona ao campo "Checksum" do datagrama */
				checksum = ;

				/* Define o valor do campo Type */
				type = ;

				/* Adiciona os campos "Checksum" e "Tipo" ao cabeçalho de enlace do datagrama */
				FrameEnlace.checksum = checksum;
				FrameEnlace.type = type;

				/* Envia o datagrama */
				sendto_garbled(FrameEnlace);

				/* Preenche o campo "feedback" do buffer compartilhado para avisar a Camada de Rede
				 * que o datagrama foi enviado com sucesso */
				bufS_RE.fdb.feedback = 0;

				/* Desboqueia thread da Camada de Rede que envia o datagrama */


			}

			/* Se o MTU não for compativel, retorna um erro para a camada de cima */
			else
			{
				/* Preenche o campo "feedback" do buffer compartilhado para avisar a Camada de Rede
				 * que o datagrama nao foi enviado pois o MTU era incompativel */
				bufS_RE.fdb.feedback = 2;

				/* Preenche o campo "mtu_size" com o valor do MTU suportado pelo enlace para que
				 * a Camada de Rede possa fragmentar o datagrama */
				bufS_RE.fdb.mtu_size = mtu;

				/* Desboqueia thread da Camada de Rede que envia o datagrama */


			}
		}

		/* Se o nó não for vizinho, retorna um erro para a camada de cima */
		else
		{
			/* Preenche o campo "feedback" do buffer compartilhado para avisar a Camada de Rede que
			 * o datagrama nao foi enviado pois o nó nao era vizinho */
			bufS_RE.fdb.feedback = 1;

			/* Desboqueia thread da Camada de Rede que envia o datagrama */

		}

		/* Fecha o socket */
		close(s);

	}
	pthread_exit(NULL);
}


/*
 * Thread que recebe o datagrama da Camada Física, o processa e depois o envia para a Camada de Rede
 */
void *recebe(void *id)
{
	/* Recebe da Camada Fisica o datagrama */
	recvfrom(s, &datagramFisica, sizeof(datagramFisica), 0, (struct sockaddr *)&dst, &dst_address_size)

	/* Calcula o Checksum e compara o resultado do cálculo ao valor presente no campo “Checksum” do datagrama
	 * Se os valores forem diferentes, descarta o datagrama */


	/* Verifica o valor presente no campo “Tipo” do datagrama, armazenando-o em uma variável */


	/* Envia o datagrama para a camada de rede (coloca no buffer compartilhado) */


}


/*
 * Camada de Enlace
 */
int enlace(CI, node)
{
	pthread_t id_send;
	pthread_t id_receive;
	int ts, tr;

	/* Leitura do arquivo CI e armazenamento das informações na memória */
	loadCI(CI);


	/* Criacao da thread que envia */
	ts = pthread_create(id_send, NULL, send, (void *)node);
	if (te)
	{
		printf("ERRO: impossivel criar thread que envia\n");
		exit(-1);
	}

	/* Criacao da thread que recebe */
	tr = pthread_create(id_receive, NULL, receive, (void *)node);
	if (tr)
	{
		printf("ERRO: impossivel criar thread que recebe\n");
		exit(-1);
	}

	pthread_exit(NULL);
	return 1;
}
